/*  This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2.1 of the License, or (at
    your option) any later version.
    For more details, see the GNU Lesser General Public License (www.fsf.org
    or the COPYING file somewhere in the package)
 */

#include "Version.hh"

/*! @file src/OSGi/Version.cc
    @brief Class OSGi::Version (non-inline) methods.
    @author @ref Guillaume_Terrissol
    @date 12th February 2010 - 22nd January 2014
    @note This file is distributed under the LGPL license.
    Refer to the file COPYING (or http://www.fsf.org) for more information.
 */

#include <sstream>

#include "Exception.hh"

namespace OSGi
{
//------------------------------------------------------------------------------
//                      Version : Constructors & Destructor
//------------------------------------------------------------------------------

    /// @cond DEVELOPMENT

    /*! Empty version (invalid).
     */
    Version::Version()
        : mMin{ 0, 0, 0, false }
        , mMax{ 0, 0, 0, false }
    { }


    /*! @param pVersion Textual version
     */
    Version::Version(const std::string& pVersion)
        : mMin{ 0, 0, 0, true }
        , mMax{ 0, 0, 0, true }
    {
        int lFigures[3] = { };
        if      (isLabel(pVersion))
        {
            readVersion(pVersion, lFigures);
            mMin = mMax = lFigures;
        }
        else if (isRange(pVersion))
        {
            mMin.mInc  = (pVersion[0] == '[');
            size_t lComma   = readVersion(pVersion.substr(1), lFigures);
            mMin = lFigures;    //                       [|)           ,
            size_t  lLast   = readVersion(pVersion.substr(1 + lComma + 1), lFigures) + 1 + lComma + 1;
            mMax = lFigures;
            mMax.mInc  = (pVersion[lLast] == ']');
        }
        else
        {
            throw Exception{"Invalid version : " + pVersion};
        }
    }


    /*! Defaulted.
     */
    Version::~Version() = default;


//------------------------------------------------------------------------------
//                       Version : Pieces of Informations
//------------------------------------------------------------------------------

    /*! @retval true  If the version is empty (invalid)
        @retval false Otherwise
     */
    bool Version::isEmpty() const
    {
        return (mMin == mMax) && (mMin.mMaj == 0) && (mMin.mMin == 0) && (mMin.mRev == 0) && (mMin.mInc == false);
    }


    /*! @retval true  If the version is unique (i.e. min == max)
        @retval false Otherwise
     */
    bool Version::isLabel() const
    {
        return (mMin == mMax);
    }


    /*! @retval true  If the version is a range (i.e. min != max)
        @retval false Otherwise
     */
    bool Version::isRange() const
    {
        return !isLabel();
    }


    /*! @param pOther Version to check
        @retval true  If @b this is a range and contains @p pOther
        @retval true  If @b this is a unique version and equals @p pOther
        @retval false If @b this is a range and doesn't contain @p pOther
        @retval false If @b this is a unique version and doesn't equal @p pOther
     */
    bool Version::contains(const Version& pOther) const
    {
        if (pOther.isEmpty())
        {
            return true;
        }
        if      (isEmpty())
        {
            return false;
        }
        else if (isRange())
        {
            bool    lAboveMin = false;
            bool    lBelowMax = false;
            if (mMin.mInc)
            {
                lAboveMin = (mMin <= pOther.mMin);
            }
            else
            {
                lAboveMin = (mMin < pOther.mMin) || (mMin == pOther.mMin);
            }

            if (mMax.mInc)
            {
                lBelowMax = (pOther.mMax <= mMax);
            }
            else
            {
                lBelowMax = (pOther.mMax < mMax) || (mMax == pOther.mMax);
            }

            return lAboveMin && lBelowMax;
        }
        else    // if (isLabel())
        {
            return (mMax == pOther.mMin) && (mMin == pOther.mMax);
        }
    }


    /*! @return The version, as text
     */
    std::string Version::toString() const
    {
        if (isLabel())
        {
            return std::to_string(mMin.mMaj) + '.' + std::to_string(mMin.mMin) + '.' + std::to_string(mMin.mRev);
        }
        else
        {
            return (mMin.mInc ? '[' : '(')
                 + std::to_string(mMin.mMaj) + '.' + std::to_string(mMin.mMin) + '.' + std::to_string(mMin.mRev) + ','
                 + std::to_string(mMax.mMaj) + '.' + std::to_string(mMax.mMin) + '.' + std::to_string(mMax.mRev)
                 + (mMax.mInc ? ']' : ')');
        }
    }


//------------------------------------------------------------------------------
//                  Version : "External" Pieces of Informations
//------------------------------------------------------------------------------

    /*! Parses a literal version to check whether it is a unique version
        @param pVersion Text to parse
        @retval true  If @p pVersion is a unique version
        @retval false Otherwise
     */
    bool Version::isLabel(const std::string& pVersion)
    {
        int lFigures[3] = { };
        return (readVersion(pVersion, lFigures) == pVersion.length());
    }


    /*! Parses a literal version to check whether it is a range of versions.
        @param pVersion Text to parse
        @retval true  If @p pVersion is a range of versions
        @retval false Otherwise
     */
    bool Version::isRange(const std::string& pVersion)
    {
        int lFigures[3] = { };
        if ((pVersion[0] == '(') || (pVersion[0] == '['))
        {
            size_t  lPos = readVersion(pVersion.substr(1), lFigures);
            if (lPos != std::string::npos)
            {   //                                [|)         ,
                lPos = readVersion(pVersion.substr(1 + lPos + 1), lFigures) + 1 + lPos + 1;

                if ((lPos != std::string::npos) && (lPos < pVersion.length()))
                {
                    return (pVersion[lPos] == ')') || (pVersion[lPos] == ']');
                }
            }
        }

        return false;
    }


//------------------------------------------------------------------------------
//                            Version : Other Method
//------------------------------------------------------------------------------

    /*! Attempts to read a version from a string.
        @param pVersion String to parse
        @param pFigures Array to set with read versions
        @return Position in @p pVersion after reading
        @note It it possible to read a range with this method, calling it twice
     */
    size_t Version::readVersion(const std::string& pVersion, int pFigures[3])
    {
        std::string lDigits;
        size_t      lPos = 0;
        for(size_t lFigure = 0; lFigure < 3; ++lFigure)
        {
            lDigits.clear();
            while(lPos < pVersion.length() && std::isdigit(pVersion[lPos]))
            {
                lDigits += pVersion[lPos++];
            }
            // As long as there are figures to read, parses the next character.
            lPos += (lFigure < 2) ? 1 : 0;
            if (lDigits.empty())
            {
                return std::string::npos;
            }
            pFigures[lFigure] = std::stol(lDigits);
        }

        return lPos;
    }


//------------------------------------------------------------------------------
//                                Bound : Methods
//------------------------------------------------------------------------------

    /*! @param pMaj Major version number
        @param pMin Minor version number
        @param pRev Revision number
        @param pInc Included bound ?
     */
    Version::Bound::Bound(uint8_t pMaj, uint8_t pMin, uint8_t pRev, bool pInc)
        : mMaj{pMaj}
        , mMin{pMin}
        , mRev{pRev}
        , mInc{pInc}
    { }


    /*! @param pFigures Array of 3 version numbers (major, minor and revision,
               in this order)
        @note Included version
     */
    Version::Bound& Version::Bound::operator=(int pFigures[3])
    {
        mMaj = pFigures[0];
        mMin = pFigures[1];
        mRev = pFigures[2];

        return *this;
    }


//------------------------------------------------------------------------------
//                               Bound : Operators
//------------------------------------------------------------------------------

    /*! @param pL Left operand
        @param pR Right operand
     */
    bool operator<(const Version::Bound& pL, const Version::Bound& pR)
    {
        if      (pL.mMaj < pR.mMaj)
        {
            return true;
        }
        else if (pR.mMaj < pL.mMaj)
        {
            return false;
        }
        else
        {
            if      (pL.mMin < pR.mMin)
            {
                return true;
            }
            else if (pR.mMin < pL.mMin)
            {
                return false;
            }
            else
            {
                return (pL.mRev < pR.mRev);
            }
        }
    }


    /*! @param pL Left operand
        @param pR Right operand
     */
    bool operator==(const Version::Bound& pL, const Version::Bound& pR)
    {
        return (pL.mMaj == pR.mMaj) && (pL.mMin == pR.mMin) && (pL.mRev == pR.mRev) && (pL.mInc == pR.mInc);
    }


    /*! @param pL Left operand
        @param pR Right operand
     */
    bool operator<=(const Version::Bound& pL, const Version::Bound& pR)
    {
        if      (pL.mMaj < pR.mMaj)
        {
            return true;
        }
        else if (pR.mMaj < pL.mMaj)
        {
            return false;
        }
        else
        {
            if      (pL.mMin < pR.mMin)
            {
                return true;
            }
            else if (pR.mMin < pL.mMin)
            {
                return false;
            }
            else
            {
                return (pL.mRev <= pR.mRev);
            }
        }
    }


//------------------------------------------------------------------------------
//                           Additional Documentation
//------------------------------------------------------------------------------


    /*! @class Version
        This class allows simplifying the bundle version numbers handling
        (comparison, range checking, etc).
     */

    /// @endcond
}
